#include "tftp.h"
#include "logcat.h"

int get_socket_and_bind(char *address, char *port) {
	struct addrinfo *res;
	struct addrinfo *resorig;
	struct addrinfo hints;

	memset(&hints, 0, sizeof (hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE;

	int error;
	if ((error = getaddrinfo(address, port, &hints, &res)) != 0) {
		errx(EXIT_FAILURE, "%s", gai_strerror(error));
	}

	int sock;

	for (resorig = res; res != NULL; res = res->ai_next) {
		if (res->ai_family != AF_INET && res->ai_family != AF_INET6) {
			continue;
		}

		sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (sock == -1) {
			warn("Could not create socket");
			continue;
		}

		if (bind(sock, res->ai_addr, res->ai_addrlen) != 0) {
			warn("Could not bind socket");
			continue;
		}

		break;
	}

	if (res == NULL) {
		errx(EXIT_FAILURE, "Exiting because of the warnings above.");
	}

	freeaddrinfo(resorig);

	int opt = 1;
	errno = 0;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));
	if (errno) {
		perror("Could not reuse addr");
	}
	return (sock);
}

void print_error_msg(char * buf) {
	short err_code = get_ushort(buf + 2);
	char msg[PACKET_MAX_LENGTH - 3]; // -3 in order when someone sends error msg without the null at the end

	int i = 0;
	while (buf[i + 4]) {
		msg[i] = buf[i + 4];
		i++;
	}
	msg[i] = '\0';

	char * err_type;
	switch (err_code) {
		case NOT_DEFINED:
			err_type = "Not defined, see error message (if any).";
			break;
		case FILE_NOT_FOUND:
			err_type = "File not found.";
			break;
		case ACCESS_VIOLATION:
			err_type = "Access violation.";
			break;
		case DISK_FULL:
			err_type = "Disk full or allocation exceeded.";
			break;
		case ILLEGAL_OP:
			err_type = "Illegal TFTP operation.";
			break;
		case UNKNOWN_ID:
			err_type = "Unknown transfer ID.";
			break;
		case FILE_EXISTS:
			err_type = "File already exists.";
			break;
		case UNKNOWN_USER:
			err_type = "No such user";
			break;
		default:
			err_type = "Unknown error";
	}

	printf("%s : %s\n", err_type, msg);
}

int does_file_exist(const char *filename) {
    struct stat st;
    return (stat(filename, &st) == 0);
}

int does_file_exist_and_is_regular(const char * filename) {
	struct stat st;
    int result = stat(filename, &st);
    return (result == 0 && S_ISREG(st.st_mode));
}

int does_dir_exist(const char *dir) {
	struct stat st;
	int result = stat(dir, &st);
	return (result == 0 && S_ISDIR(st.st_mode));
}

void send_error_msg_to(int sock, error_code err_code, char *msg, struct sockaddr *addr, socklen_t addrlen) {
	char * buf = malloc(PACKET_MAX_LENGTH);
	set_ushort(buf, ERROR);
	set_ushort(buf + 2, err_code);
	int indent = set_char_arr(buf, msg, 4, 1);

	if (sendto(sock, buf, indent, 0, addr, addrlen) == -1) {
		err(EXIT_FAILURE, "Send error.");
	}

	free(buf);
}

void send_error_msg(int sock, error_code err_code, char *msg) {
	char * buf = malloc(516);
	set_ushort(buf, ERROR);
	set_ushort(buf + 2, err_code);
	int indent = set_char_arr(buf, msg, 4, 1);

	if (send(sock, buf, indent, 0) == -1) {
		err(EXIT_FAILURE, "Send error.");
	}

	free(buf);
}

void string_to_upper(char * buf) {
	int i = 0;
	while (buf[i]) {
		buf[i] = toupper(buf[i]);
		i++;
	}
}

int get_socket(char *addr, struct addrinfo **res, struct addrinfo **res_orig, char *port) {
	struct addrinfo *res_;
	struct addrinfo *resorig_;
	struct addrinfo hints;

	memset(&hints, 0, sizeof (hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE;

	int error;
	if ((error = getaddrinfo(addr, port, &hints, &res_)) != 0) {
		errx(EXIT_FAILURE, "%s", gai_strerror(error));
	}

	int sock;

	for (resorig_ = res_; res_ != NULL; res_ = res_->ai_next) {
		if (res_->ai_family != AF_INET && res_->ai_family != AF_INET6) {
			continue;
		}

		sock = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
		if (sock == -1) {
			warn("Could not create socket");
			continue;
		}

		break;
	}

	if (res_ == NULL) {
		errx(EXIT_FAILURE, "Exitting because of the warnings above");
	}

	int opt = 1;
	int saved_errno = errno;
	errno = 0;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));
	if (errno) {
		perror("Could not reuse addr");
	}
	errno = saved_errno;

	*res = res_;
	*res_orig = resorig_;
	return (sock);
}

in_port_t get_in_port(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET) {
		return (((struct sockaddr_in *)sa)->sin_port);
	}
	return (((struct sockaddr_in6 *)sa)->sin6_port);
}

void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return (&((struct sockaddr_in *)sa)->sin_addr);
	}
	return (&((struct sockaddr_in6 *)sa)->sin6_addr);
}

uint16_t get_ushort(char *buff) {
	return (ntohs((((uint16_t) buff[1]) << 8) + buff[0]));
}

void set_ushort(char *buf, uint16_t number) {
	uint16_t num = htons(number);
	buf[0] = num & 0xff;
	buf[1] = (num >> 8) & 0xff;
}

int set_char_arr(char *buf, char *arr, int indent, int append_null) {
	int len = (int) strlen(arr);
	for (int i = 0; i < len; i++) {
		buf[i + indent] = arr[i];
	}
	if (append_null) {
		buf[indent + len] = '\0';
		return (indent + len + 1);
	}
	return (indent + len);
}

int send_data(char *buff, char *netascii_buff, int sock, int *from_previous, FILE *f, mode_type mode, uint16_t packets_sent) {
	set_ushort(buff, DATA);
	set_ushort(buff + 2, packets_sent);
	int read = fread(buff + 4, 1, PACKET_MAX_LENGTH - 4 - *from_previous, f);
	int bytes_to_send = 0;

	if (mode == NETASCII) {
		int p = 0;

		for (int i = 0; i < *from_previous; i++) {
			netascii_buff[p++] = netascii_buff[PACKET_MAX_LENGTH - 4 + i];
		}

		for (int i = 4; i < read + 4; i++) {
			if (buff[i] == '\r') {
				netascii_buff[p++] = '\r';
				netascii_buff[p++] = '\0';
			} else if (buff[i] == '\n') {
				netascii_buff[p++] = '\r';
				netascii_buff[p++] = '\n';
			} else {
				netascii_buff[p++] = buff[i];
			}
		}

		if (p > PACKET_MAX_LENGTH - 4) {
			bytes_to_send = PACKET_MAX_LENGTH;
			*from_previous = p - PACKET_MAX_LENGTH + 4;
		} else {
			bytes_to_send = p + 4;
			*from_previous = 0;
		}

		memcpy(buff + 4, netascii_buff, PACKET_MAX_LENGTH - 4);

		if (bytes_to_send - 4 > 0) {
			if (send(sock, buff, bytes_to_send, 0) == -1) {
				err(EXIT_FAILURE, "Send error.");
			}
		}
	} else if (mode == OCTET) {
		bytes_to_send = read + 4;
		if (bytes_to_send - 4 > 0) {
			if (send(sock, buff, bytes_to_send, 0) == -1) {
				err(EXIT_FAILURE, "Send error.");
			}
		}
	} else {
		errx(EXIT_FAILURE, "Unknown mode");
	}

	return (bytes_to_send);
}

void convert_from_net_and_write(char *buf, FILE *f, int *cr_at_the_end, int n) {
	int i = 4;
	int p = 0;

	char new_buf[PACKET_MAX_LENGTH - 4];

	if (*cr_at_the_end) {
		if (buf[i] == '\n') {
			new_buf[p++] = '\n';
		} else if (buf[i] == '\0') {
			new_buf[p++] = '\r';
		} else {
			errx(EXIT_FAILURE, "wrong netascii format");
		}
		i = 5;
		*cr_at_the_end = 0;
	}

	for (; i < n; i++) {
		if (buf[i] == '\r') {
			if (i == PACKET_MAX_LENGTH - 1) { // need info from next packet
				*cr_at_the_end = 1;
			} else {
				if (buf[i + 1] == '\n') {
					new_buf[p++] = '\n';
					i++;
				} else if (buf[i + 1] == '\0') {
					new_buf[p++] = '\r';
					i++;
				} else {
					errx(EXIT_FAILURE, "wrong netascii format");
				}
			}
		} else {
			new_buf[p++] = buf[i];
		}
	}
	fwrite(new_buf, 1, p, f);
}

int compare(char *file1, char *file2) {
	FILE *f1 = fopen(file1, "r");
	if (f1 == NULL) {
		errx(EXIT_FAILURE, "Could not open file: %s", file1);
	}

	FILE *f2 = fopen(file2, "r");
	if (f2 == NULL) {
		errx(EXIT_FAILURE, "Could not open file: %s", file2);
	}

	char *buf1 = malloc(1024);
	if (buf1 == NULL) {
		errx(EXIT_FAILURE, "Could not allocate memory.");
	}

	char *buf2 = malloc(1024);
	if (buf2 == NULL) {
		errx(EXIT_FAILURE, "Could not allocate memory.");
	}

	while (1) {
		int n1 = fread(buf1, 1, 1024, f1);
		int n2 = fread(buf2, 1, 1024, f2);
		if (n1 != n2) {
			fclose(f1);
			fclose(f2);
			return (0);
		}

		if (n1 == 0) {
			fclose(f1);
			fclose(f2);
			return (1);
		}

		if (n1 < 1024) {
			fclose(f1);
			fclose(f2);
			return (!memcmp(buf1, buf2, n1));
		}

		if (memcmp(buf1, buf2, 1024)) {
			fclose(f1);
			fclose(f2);
			return (0);
		}
	}
}
